<template>
    <div class="graph3d-container">
        <button v-if="showBackButton" class="back-button" @click="goBack">返回</button>
        <div ref="graphContainer" class="graph3d-canvas"></div>
    </div>
</template>

<script setup>
    import { onMounted, ref, onBeforeUnmount, watch } from 'vue'
    import ForceGraph3D from '3d-force-graph'
    import * as THREE from 'three'

    const props = defineProps({
        graphData: {
            type: Object,
            required: true
        }
    });
    const graphData = props.graphData;

    const graphContainer = ref(null)


    let Graph = null
    let nodeSpriteMap = new Map(); // 记录每个节点的sprite和球体

    // 用于控制是否显示返回按钮
    const showBackButton = ref(false);
    // 存储当前聚焦的第二层节点ID，null 表示在初始视图
    const focusedNodeId = ref(null);
    // 用于控制是否显示完整视图
    const showFullGraph = ref(false);

    // 1. 定义全局变量，保存初始视图节点坐标
    let fixedInitialCoords = [];
    const DEFAULT_VELOCITY_DECAY = 0.4; // 默认力导向参数

    // 连接线点击处理函数
    const handleLinkClick = (link) => {
        // 只允许完整视图下可点击
        if (!showFullGraph.value) return;
        // 始终通过fullGraphData的nodeMap获取目标节点对象
        const nodeMap = new Map(graphData.nodes.map(node => [node.id, node]));
        const targetId = typeof link.target === 'object' ? link.target.id : link.target;
        const targetNode = nodeMap.get(targetId);
        if (targetNode && targetNode.level === 2) {
            // 切换该节点的掌握状态
            targetNode.isMastered = !targetNode.isMastered;

            // 递归更新父节点的掌握状态
            updateParentMasteryStatus(targetNode);

            // 更新连接线颜色
            updateLinkColors();
        }
    };

    // 更新连接线颜色的函数
    const updateLinkColors = () => {
        const nodeMap = new Map(graphData.nodes.map(node => [node.id, node]));
        Graph.linkColor(link => {
            const targetId = typeof link.target === 'object' ? link.target.id : link.target;
            const targetNode = nodeMap.get(targetId);
            // 如果目标节点是第二层或第三层，并且它被标记为"已掌握"
            if (targetNode && (targetNode.level === 1 || targetNode.level === 2) && targetNode.isMastered) {
                return '#00ff00'; // 绿色
            }
            return '#ffffff'; // 其他连接线保持白色
        });
        Graph.refresh(); // 强制刷新显示
    };

    // 递归更新父节点状态的函数
    const updateParentMasteryStatus = (childNode) => {
        const parentId = childNode.parentId;
        const parentNode = graphData.nodes.find(n => n.id === parentId);

        if (parentNode) {
            // 检查该父节点的所有子节点是否都已掌握
            const allChildren = graphData.nodes.filter(n => n.parentId === parentId);
            const allMastered = allChildren.every(child => child.isMastered);

            // 更新父节点的掌握状态
            parentNode.isMastered = allMastered;

            // 递归更新更上层的父节点
            if (parentNode.parentId) {
                updateParentMasteryStatus(parentNode);
            }
        }
    };

    // Demo数据
    // const _generateFullGraphData = () => { ... } // 整个函数注释掉

    // 根据当前视图模式生成要显示的图谱数据
    const getGraphDataForDisplay = () => {
        if (!graphData || !graphData.nodes || !graphData.links) return { nodes: [], links: [] };
        const visibleNodesMap = new Map();
        const visibleLinksMap = new Map();
        const nodeMap = new Map(graphData.nodes.map(node => [node.id, node]));

        if (showFullGraph.value) {

            let nodes = graphData.nodes;
            let links = graphData.links
                .map(link => ({
                    ...link,
                    source: typeof link.source === 'object' ? link.source.id : link.source,
                    target: typeof link.target === 'object' ? link.target.id : link.target
                }))
                .filter(link => nodeMap.get(link.source) && nodeMap.get(link.target)); // 跳过无效连线
            return { nodes, links };
        }

        if (focusedNodeId.value) {


            // 递归收集节点和连线的函数
            const collectNodesAndLinks = (nodeId) => {
                const node = nodeMap.get(nodeId);
                if (!node) return;

                // 添加当前节点
                visibleNodesMap.set(nodeId, node);

                // 收集所有以当前节点为源的连线
                graphData.links.forEach(link => {
                    const sourceId = typeof link.source === 'object' ? link.source.id : link.source;
                    const targetId = typeof link.target === 'object' ? link.target.id : link.target;

                    if (sourceId === nodeId) {
                        // 添加连线
                        visibleLinksMap.set(`${sourceId}-${targetId}`, {
                            ...link,
                            source: sourceId,
                            target: targetId
                        });

                        // 递归收集目标节点
                        collectNodesAndLinks(targetId);
                    }
                });
            };

            // 从聚焦节点开始递归收集
            collectNodesAndLinks(focusedNodeId.value);

            return { nodes: Array.from(visibleNodesMap.values()), links: Array.from(visibleLinksMap.values()) };
        } else {

            // 初始视图：只显示 Level 0 和 Level 1 节点及其链接
            graphData.nodes.forEach(node => {
                if (node.level <= 1) {
                    visibleNodesMap.set(node.id, node);
                }
            });
            graphData.links.forEach(link => {
                const sourceId = typeof link.source === 'object' ? link.source.id : link.source;
                const targetId = typeof link.target === 'object' ? link.target.id : link.target;
                const sourceNode = nodeMap.get(sourceId);
                const targetNode = nodeMap.get(targetId);
                if (sourceNode && targetNode && sourceNode.level <= 1 && targetNode.level <= 1) {
                    visibleLinksMap.set(`${sourceId}-${targetId}`, {
                        ...link,
                        source: sourceId,
                        target: targetId
                    });
                }
            });
        }

        let nodes = Array.from(visibleNodesMap.values());
        let links = Array.from(visibleLinksMap.values());
        // 确保links的source/target始终为字符串id
        links = links.map(link => ({
            ...link,
            source: typeof link.source === 'object' ? link.source.id : link.source,
            target: typeof link.target === 'object' ? link.target.id : link.target
        }));

        return {
            nodes,
            links
        };
    };

    // 根据当前显示节点的最大层级数动态设置连接线长度
    function setDynamicLinkDistance() {
        const nodes = Graph.graphData().nodes;
        const maxLevel = Math.max(...nodes.map(n => n.level || 0), 0);
        let dist = 100;
        if (maxLevel === 2) dist = 100;
        else if (maxLevel === 3) dist = 80;
        else if (maxLevel === 4) dist = 60;
        else if (maxLevel >= 5) dist = 40;
        Graph.d3Force('link').distance(dist);
    }

    // 返回初始视图函数
    const goBack = () => {
        if (!graphData || !graphData.nodes) return;
        focusedNodeId.value = null;
        showBackButton.value = false;

        // 恢复初始视图固定坐标
        restoreInitialCoords();
        // 关闭力导向模拟，锁定节点坐标
        if (Graph) Graph.d3VelocityDecay(1);

        const newGraphData = getGraphDataForDisplay();
        Graph.graphData(JSON.parse(JSON.stringify(newGraphData)));

        // 动态设置连接线长度
        setDynamicLinkDistance();

        // 相机居中
        const firstNode = graphData.nodes[0];
        if (firstNode) {
            Graph.cameraPosition(
                { x: 0, y: 0, z: 1200 },
                { x: 0, y: 0, z: 0 },
                600
            );
        }
        Graph.refresh();
        // 不再调用Graph.d3ReheatSimulation();
        updateLinkColors();
    };

    // 中心节点点击切换完整/初始视图
    const handleNodeClick = node => {

        // 如果点击的是根节点 (level 0)，切换完整/初始视图
        if (node.level === 0) {
            showFullGraph.value = !showFullGraph.value;
            focusedNodeId.value = null;
            showBackButton.value = false;
            Graph.graphData(JSON.parse(JSON.stringify(getGraphDataForDisplay())));
            // 动态设置连接线长度
            setDynamicLinkDistance();
            if (showFullGraph.value) {
                // 切换到完整视图
                Graph.d3Force('link').distance(30);
            } else {
                // 切回初始视图
                Graph.d3Force('link').distance(80);
            }
            // 切换到分支/完整视图时恢复默认力导向
            if (Graph) Graph.d3VelocityDecay(DEFAULT_VELOCITY_DECAY);
            Graph.cameraPosition(
                { x: node.x, y: node.y, z: node.z + 320 },
                node,
                400
            );
            Graph.refresh();
            Graph.d3ReheatSimulation();
            updateLinkColors();

            // ====== 保证分支节点全部可见 ======
            setTimeout(() => {
                Graph.zoomToFit(400, 0);
            }, 500); // 等力导向初步收敛后再执行
            // ====== END ======

            return;
        }

        // 如果点击的是第二层节点 (level 1)
        if (node.level === 1) {
            // 如果点击的节点已经是当前聚焦的节点，则只居中，不刷新图谱数据
            if (focusedNodeId.value === node.id) {
                let cameraZOffset = 200; // 第二层节点保持现有偏移量
                Graph.cameraPosition(
                    { x: node.x, y: node.y, z: node.z + cameraZOffset },
                    node,
                    400
                );
                return; // 执行完居中后直接返回，避免后面的图谱数据刷新
            }

            // 如果是点击了新的第二层节点，则更新聚焦节点，刷新图谱数据，并居中相机
            focusedNodeId.value = node.id; // 设置当前聚焦的第二层节点ID
            showBackButton.value = true; // 显示返回按钮
            showFullGraph.value = false;
            Graph.graphData(JSON.parse(JSON.stringify(getGraphDataForDisplay()))); // 更新图谱数据为分支视图
            // 动态设置连接线长度
            setDynamicLinkDistance();
            Graph.refresh(); // 确保图谱布局刷新
            updateLinkColors(); // 更新连接线颜色

            // ====== 保证分支节点全部可见 ======
            setTimeout(() => {
                Graph.zoomToFit(400, 0);
            }, 500); // 等力导向初步收敛后再执行
            // ====== END ======

            let cameraZOffset = 200; // 第二层节点保持现有偏移量
            // 移动相机到点击的节点位置，并将其设为旋转中心
            Graph.cameraPosition(
                { x: node.x, y: node.y, z: node.z + cameraZOffset },
                node, // 相机看向点击的节点
                400 // 400毫秒的平滑过渡动画
            );
            // 切换到分支/完整视图时恢复默认力导向
            if (Graph) Graph.d3VelocityDecay(DEFAULT_VELOCITY_DECAY);
            return;
        }

        // 其他节点的处理
        if (Graph) Graph.d3VelocityDecay(DEFAULT_VELOCITY_DECAY);
        let cameraZOffset = 200; // 默认偏移量
        // 移动相机到点击的节点位置，并将其设为旋转中心
        Graph.cameraPosition(
            { x: node.x, y: node.y, z: node.z + cameraZOffset },
            node, // 相机看向点击的节点
            400 // 400毫秒的平滑过渡动画
        );
    };

    onMounted(() => {
        // 1. 获取完整的图谱数据
        // initialGraphData = _generateFullGraphData();
        // 只在初始化时深拷贝一次
        // fullGraphData = JSON.parse(JSON.stringify(initialGraphData));

        Graph = ForceGraph3D()(graphContainer.value)
            .graphData(JSON.parse(JSON.stringify(getGraphDataForDisplay()))) // 初始只显示两层
            .backgroundColor('#181828') // 深色背景
            .nodeAutoColorBy(undefined) // 关闭自动着色
            .nodeLabel('') // 关闭默认label
            .linkOpacity(0.5)
            .linkDirectionalParticles(2)  // 粒子数量
            .linkDirectionalParticleWidth(1)    // 粒子宽度
            .linkDirectionalParticleSpeed(0.005)    //  粒子速度
            .linkWidth(1)
            .linkColor(link => {
                const nodeMap = new Map(graphData.nodes.map(node => [node.id, node]));
                const targetId = typeof link.target === 'object' ? link.target.id : link.target;
                const targetNode = nodeMap.get(targetId);
                // 如果目标节点是第二层或第三层，并且它被标记为"已掌握"
                if (targetNode && (targetNode.level === 1 || targetNode.level === 2) && targetNode.isMastered) {
                    return '#00ff00'; // 绿色
                }
                return '#ffffff'; // 其他情况为白色
            })
            .width(window.innerWidth)
            .height(window.innerHeight)
            .nodeThreeObject(node => {

                // 中心节点高质量发光球体+刻字+立体感
                if (node.level === 0) {
                    // 参数配置
                    const mainColor = 0xFF5C5C; // 主色（红）
                    const glowColor = 0xFF9A76; // 辉光色（橙红）
                    const radius = 16;          // 保持与原尺寸一致

                    // 1. 主球体 - 渐变透明材质
                    const sphereMat = new THREE.MeshPhongMaterial({
                        color: mainColor,
                        emissive: glowColor,
                        emissiveIntensity: 1.5,  // 增强自发光
                        specular: 0xFFFFFF,     // 高光反射
                        shininess: 100,
                        transparent: false,
                        opacity: 0.9,
                        depthWrite: true       // 防止透明材质闪烁
                    });

                    // 2. 内发光层 - 增强中心高亮
                    const innerGlowMat = new THREE.MeshBasicMaterial({
                        color: glowColor,
                        side: THREE.BackSide,
                        transparent: true,
                        opacity: 0.4,
                        depthWrite: false
                    });
                    const innerGlow = new THREE.Mesh(
                        new THREE.SphereGeometry(radius * 0.9, 32, 32),
                        innerGlowMat
                    );

                    // 3. 外光晕 - 模拟霓虹灯效果
                    const outerGlowMat = new THREE.ShaderMaterial({
                        uniforms: {
                            glowColor: { value: new THREE.Color(glowColor) }
                        },
                        vertexShader: `
                            varying vec3 vNormal;
                            void main() {
                                vNormal = normalize(normalMatrix * normal);
                                gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                            }
                        `,
                        fragmentShader: `
                            uniform vec3 glowColor;
                            varying vec3 vNormal;
                            void main() {
                                float intensity = pow(1.0 - abs(dot(vNormal, vec3(0,0,1))), 4.0);
                                gl_FragColor = vec4(glowColor, intensity * 0.6);
                            }
                        `,
                        side: THREE.BackSide,
                        blending: THREE.AdditiveBlending,
                        transparent: true,
                        depthWrite: false
                    });
                    const outerGlow = new THREE.Mesh(
                        new THREE.SphereGeometry(radius * 1.2, 32, 32),
                        outerGlowMat
                    );

                    // 4. 组合球体
                    const centerSphere = new THREE.Mesh(
                        new THREE.SphereGeometry(radius, 64, 64),
                        sphereMat
                    );
                    centerSphere.add(innerGlow, outerGlow);

                    // 5. 文字处理（Sprite方式）
                    const labelCanvas = document.createElement('canvas');
                    labelCanvas.width = 512;
                    labelCanvas.height = 128;
                    const labelCtx = labelCanvas.getContext('2d');
                    labelCtx.font = 'bold 60px sans-serif';
                    labelCtx.textAlign = 'center';
                    labelCtx.textBaseline = 'middle';
                    labelCtx.fillStyle = '#fff';
                    labelCtx.shadowColor = '#ff5c5c';
                    labelCtx.shadowBlur = 16;
                    labelCtx.fillText(node.label || node.id, 256, 64);
                    labelCtx.shadowBlur = 0;
                    const labelTexture = new THREE.CanvasTexture(labelCanvas);
                    labelTexture.needsUpdate = true;
                    const spriteMaterial = new THREE.SpriteMaterial({ map: labelTexture, transparent: true });
                    const sprite = new THREE.Sprite(spriteMaterial);
                    sprite.scale.set(radius * 3.2, radius * 1.0, 1);
                    sprite.position.set(0, radius * 1.15, 0);
                    sprite.renderOrder = 3;

                    // 组合到Group
                    const group = new THREE.Group();
                    group.add(centerSphere);
                    group.add(sprite);
                    // 记录sprite和球体引用
                    nodeSpriteMap.set(node.id, { group, sprite, sphere: centerSphere, radius });
                    return group;
                } else {
                    // 颜色设置
                    let mainColor = '#ff0000' // 默认红色
                    if (node.level === 1) {
                        mainColor = '#a259ff' // 第二层节点紫色
                    } else if (node.level === 2) {
                        mainColor = '#3fa7ff' // 第三层节点蓝色
                    } else if (node.level === 3) {
                        mainColor = '#a259ff' // 第四层节点粉色
                    } else if (node.level === 4) {
                        mainColor = '#3fa7ff' // 第五层节点浅蓝色
                    } else if (node.level === 5) {
                        mainColor = '#42ff8b' // 第六层节点小麦色
                    }
                    // 尺寸设置
                    let radius = 16 // 默认尺寸
                    if (node.level === 1) radius = 12 // 第二层节点
                    if (node.level === 2) radius = 11  // 第三层节点
                    if (node.level === 3) radius = 10  // 第四层节点
                    if (node.level === 4) radius = 8  // 第五层节点
                    if (node.level === 5) radius = 7  // 第六层节点

                    // 2. 创建球体（Lambert材质+emissive+透明）
                    const sphereGeometry = new THREE.SphereGeometry(radius, 64, 64)
                    const sphereMaterial = new THREE.MeshLambertMaterial({
                        color: new THREE.Color(mainColor),
                        emissive: new THREE.Color(mainColor).offsetHSL(0, 0.2, 0.1),
                        emissiveIntensity: 0.3,
                        transparent: true,
                        opacity: 0.95
                    })
                    const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
                    sphere.renderOrder = 2

                    // 2.5. 创建Glow外壳Mesh（轮廓发光，纯色透明+AdditiveBlending）
                    const glowGeometry = new THREE.SphereGeometry(radius * 1.1, 64, 64)
                    const glowMaterial = new THREE.MeshBasicMaterial({
                        color: new THREE.Color(mainColor),
                        transparent: true,
                        opacity: 0.15,
                        side: THREE.BackSide,
                        depthWrite: false,
                        blending: THREE.AdditiveBlending
                    })
                    const glowMesh = new THREE.Mesh(glowGeometry, glowMaterial)
                    glowMesh.renderOrder = 1

                    // 3. 创建文字Sprite
                    const labelCanvas = document.createElement('canvas')
                    labelCanvas.width = 512
                    labelCanvas.height = 128
                    const labelCtx = labelCanvas.getContext('2d')
                    labelCtx.font = 'bold 38px Arial'
                    labelCtx.textAlign = 'center'
                    labelCtx.textBaseline = 'middle'
                    labelCtx.fillStyle = '#fff'

                    // 动态设置阴影颜色，确保是RGBa格式
                    let shadowColorRGB = new THREE.Color(mainColor);
                    // labelCtx.shadowColor = `rgba(${Math.round(shadowColorRGB.r * 255)},${Math.round(shadowColorRGB.g * 255)},${Math.round(shadowColorRGB.b * 255)},0.7)`
                    labelCtx.shadowBlur = 15
                    // 多行处理
                    const lines = String(node.label || node.id).split(/\n| |，|,/)
                    const lineHeight = 45 // 调整行高适应新字号
                    const totalHeight = lines.length * lineHeight
                    lines.forEach((line, i) => {
                        labelCtx.fillText(line, 256, 64 - totalHeight / 2 + i * lineHeight + lineHeight / 2)
                    })
                    labelCtx.shadowBlur = 0
                    const labelTexture = new THREE.CanvasTexture(labelCanvas)
                    labelTexture.needsUpdate = true
                    // Glow Sprite用AdditiveBlending和更高亮度
                    const spriteMaterial = new THREE.SpriteMaterial({
                        map: labelTexture,
                        transparent: true,
                        blending: THREE.AdditiveBlending,
                        opacity: 0.7
                    })
                    const sprite = new THREE.Sprite(spriteMaterial)
                    sprite.scale.set(radius * 3.2, radius * 1.0, 1)
                    sprite.position.set(0, radius * 1.15, 0)
                    sprite.renderOrder = 3

                    // 1. 创建Glow Sprite
                    const glowSize = 512
                    const glowCanvas = document.createElement('canvas')
                    glowCanvas.width = glowCanvas.height = glowSize
                    const glowCtx = glowCanvas.getContext('2d')
                    const glowGradient = glowCtx.createRadialGradient(glowSize / 2, glowSize / 2, glowSize * 0.15, glowSize / 2, glowSize / 2, glowSize / 2)
                    glowGradient.addColorStop(0, mainColor + 'cc')
                    glowGradient.addColorStop(0.4, mainColor + '44')
                    glowGradient.addColorStop(1, 'rgba(0,0,0,0)')
                    glowCtx.fillStyle = glowGradient
                    glowCtx.beginPath()
                    glowCtx.arc(glowSize / 2, glowSize / 2, glowSize / 2, 0, 2 * Math.PI)
                    glowCtx.fill()
                    const glowTexture = new THREE.CanvasTexture(glowCanvas)
                    glowTexture.needsUpdate = true
                    const glowSpriteMaterial = new THREE.SpriteMaterial({
                        map: glowTexture,
                        transparent: true,
                        blending: THREE.AdditiveBlending,
                        opacity: 0.7,
                        depthWrite: false
                    })
                    const glowSprite = new THREE.Sprite(glowSpriteMaterial)
                    glowSprite.scale.set(radius * 3.5, radius * 3.5, 1)
                    glowSprite.position.set(0, 0, 0)
                    glowSprite.renderOrder = 0

                    // 组合到Group
                    const group = new THREE.Group()
                    group.add(glowSprite)
                    if (node.level === 0) { // 只有根节点才添加glowMesh
                        group.add(glowMesh)
                    }
                    group.add(sphere)
                    group.add(sprite)
                    // 记录sprite和球体引用
                    nodeSpriteMap.set(node.id, { group, sprite, sphere, radius })
                    return group
                }
            })
            .onNodeClick(handleNodeClick)
            .onLinkClick(handleLinkClick)

        // 动态更新Sprite位置，使其始终在球体表面朝向摄像机一侧
        function updateSprites() {
            if (!Graph) return; // 添加这行检查
            const camera = Graph.camera();
            nodeSpriteMap.forEach(({ group, sprite, sphere, radius }) => {
                // 计算球体世界坐标
                group.updateMatrixWorld();
                const worldPos = new THREE.Vector3();
                group.getWorldPosition(worldPos);
                // 计算摄像机到球体的方向
                const camPos = camera.position.clone();
                const dir = camPos.sub(worldPos).normalize();
                // 设置sprite在球体表面朝向摄像机一侧
                sprite.position.copy(dir.multiplyScalar(radius + 2));
            });
            requestAnimationFrame(updateSprites);
        }
        updateSprites();

        // 添加全局环境光
        Graph.scene().add(new THREE.AmbientLight(0x404040, 0.8));

        // 添加平行光
        const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
        directionalLight.position.set(1, 1, 1);
        Graph.scene().add(directionalLight);

        // 添加点光源
        const pointLight = new THREE.PointLight(0xffffff, 1);
        pointLight.position.set(0, 0, 100);
        Graph.scene().add(pointLight);

        // 初始视图设置距离
        setDynamicLinkDistance();

        // 监听窗口大小变化，保持全屏
        window.addEventListener('resize', handleResize)

        // 2. 页面加载后延时保存初始视图坐标（等力导向收敛）
        setTimeout(() => {
            saveInitialCoords();
        }, 1200); // 你可根据实际收敛速度调整
    })

    onBeforeUnmount(() => {
        window.removeEventListener('resize', handleResize)
        Graph = null
    })

    function handleResize() {
        if (Graph) {
            Graph.width(window.innerWidth)
            Graph.height(window.innerHeight)
        }
    }

    // 3. 保存初始视图坐标的方法
    function saveInitialCoords() {
        fixedInitialCoords = graphData.nodes.map(n => ({
            id: n.id,
            x: n.x,
            y: n.y,
            z: n.z
        }));
        // console.log('【调试】已保存初始视图坐标', fixedInitialCoords);
    }

    // 4. 恢复初始视图坐标的方法
    function restoreInitialCoords() {
        graphData.nodes.forEach(n => {
            const fix = fixedInitialCoords.find(f => f.id === n.id);
            if (fix) {
                n.x = fix.x;
                n.y = fix.y;
                n.z = fix.z;
            }
        });
        // console.log('【调试】已恢复初始视图坐标', graphData.nodes);
    }

    // 5. 监听节点结构变动，自动刷新固定坐标
    watch(() => graphData.nodes.length, (newLen, oldLen) => {
        // 节点增删后，恢复默认力导向，延时保存新坐标
        if (Graph) Graph.d3VelocityDecay(DEFAULT_VELOCITY_DECAY);
        setTimeout(() => {
            saveInitialCoords();
            setDynamicLinkDistance();
        }, 1200);
    });
</script>

<style scoped>
    .graph3d-container {
        position: fixed;
        left: 0;
        top: 0;
        width: 100vw;
        height: 100vh;
        background: #181828;
    }

    .back-button {
        position: absolute;
        top: 20px;
        left: 20px;
        padding: 10px 20px;
        background-color: #007bff;
        color: white;
        border: none;
        border-radius: 5px;
        cursor: pointer;
        font-size: 16px;
        z-index: 10;
    }

    .back-button:hover {
        background-color: #0056b3;
    }

    .graph3d-canvas {
        width: 100%;
        height: 100%;
    }
</style>